﻿using CommunityToolkit.Mvvm.Input;
using FFmpeg.NET;
using Newtonsoft.Json;
using Notifications.Wpf;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.IO;
using System.Text;
using System.Text.RegularExpressions;
using System.Windows.Input;
using VideoTools.Tasks;
using VideoTools.Util;
using VideoTools.vo;
using TaskStatus = VideoTools.vo.TaskStatus;

namespace VideoTools.Commands
{
    public class FFmpegCommands : NotifyPropertyBase
    {
        private static volatile FFmpegCommands _instance;
        private static readonly object SyncRoot = new object();
        static Dictionary<string, FFmpegTaskItem> Task_Context =
            new Dictionary<string, FFmpegTaskItem>();
        static Dictionary<string, IProgress<double>> Progress_Context =
            new Dictionary<string, IProgress<double>>();
        public static FFmpegCommands Instance
        {
            get
            {
                if (_instance == null)
                {
                    lock (SyncRoot)
                    {
                        if (_instance == null)
                        {
                            _instance = new FFmpegCommands();
                        }
                    }
                }

                return _instance;
            }
        }

        private FFmpegCommands()
        {
            string commandContent = File.ReadAllText(AppConstants.FFmpegCommandFile);

            List<FFmpegCommand> commands = JsonConvert.DeserializeObject<List<FFmpegCommand>>(
                commandContent
            );
            CommandList.Add(new FFmpegCommand() { name = AppConstants.QuickTips });
            foreach (FFmpegCommand c in commands)
            {
                CommandList.Add(c);
            }

            foreach (DeviceType dt in Utils.GetAllEnumValues<DeviceType>())
            {
                DeviceTypes.Add(dt);
            }
        }

        private ObservableCollection<FFmpegCommand> _CommandList =
            new ObservableCollection<FFmpegCommand>();

        public ObservableCollection<FFmpegCommand> CommandList
        {
            get => _CommandList;
            set => SetProperty(ref _CommandList, value);
        }

        private bool _ShutDownOnDoneAll;

        public bool ShutDownOnDoneAll
        {
            get => _ShutDownOnDoneAll;
            set => SetProperty(ref _ShutDownOnDoneAll, value);
        }

        private bool _ClearListEnable = true;

        public bool ClearListEnable
        {
            get => _ClearListEnable;
            set => SetProperty(ref _ClearListEnable, value);
        }

        private bool _DeviceTypeEnable = true;

        public bool DeviceTypeEnable
        {
            get => _DeviceTypeEnable;
            set => SetProperty(ref _DeviceTypeEnable, value);
        }
        private bool _DoCommandEnable = true;

        public bool DoCommandEnable
        {
            get => _DoCommandEnable;
            set => SetProperty(ref _DoCommandEnable, value);
        }
        private bool _DeleteSelectedEnable = true;

        public bool DeleteSelectedEnable
        {
            get => _DeleteSelectedEnable;
            set => SetProperty(ref _DeleteSelectedEnable, value);
        }
        private bool _CurrentCommandEnable = true;

        public bool CurrentCommandEnable
        {
            get => _CurrentCommandEnable;
            set => SetProperty(ref _CurrentCommandEnable, value);
        }
        private bool _OutputEndWithEnable = true;

        public bool OutputEndWithEnable
        {
            get => _OutputEndWithEnable;
            set => SetProperty(ref _OutputEndWithEnable, value);
        }
        private bool _CommandListEnable = true;

        public bool CommandListEnable
        {
            get => _CommandListEnable;
            set => SetProperty(ref _CommandListEnable, value);
        }

        private string _DoneCount = "已完成 0";

        public string DoneCount
        {
            get => _DoneCount;
            set => SetProperty(ref _DoneCount, value);
        }
        private string _CommandDesc = "选择合适的处理命令";

        public string CommandDesc
        {
            get => _CommandDesc;
            set => SetProperty(ref _CommandDesc, value);
        }

        private string _TaskCount = "总任务 0";

        public string TaskCount
        {
            get => _TaskCount;
            set => SetProperty(ref _TaskCount, value);
        }
        private string _CurrentCommand;

        public string CurrentCommand
        {
            get => _CurrentCommand;
            set => SetProperty(ref _CurrentCommand, value);
        }
        private string _OutputEndWith;

        public string OutputEndWith
        {
            get => _OutputEndWith;
            set => SetProperty(ref _OutputEndWith, value);
        }
        private FFmpegCommand _CurrentFFmpegCommand;

        public FFmpegCommand CurrentFFmpegCommand
        {
            get => _CurrentFFmpegCommand;
            set => SetProperty(ref _CurrentFFmpegCommand, value);
        }

        private ObservableCollection<DeviceType> _DeviceTypes =
            new ObservableCollection<DeviceType>();

        public ObservableCollection<DeviceType> DeviceTypes
        {
            get => _DeviceTypes;
            set => SetProperty(ref _DeviceTypes, value);
        }

        private DeviceType _DeviceType = DeviceType.GPU;

        public DeviceType DeviceType
        {
            get => _DeviceType;
            set => SetProperty(ref _DeviceType, value);
        }

        private int taskCount;
        private int doneCount;
        private ICommand _DoCommand;
        private ICommand _OpenFileCommand;
        private ICommand _StopAllCommand;
        private ICommand _DeleteSelectedCommand;
        private ICommand _DeleteDoneCommand;
        private ICommand _ClearListCommand;
        private ICommand _CommandChangedCommand;
        private ICommand _ChangeDeviceTypeCommand;
        private ICommand _CopyTaskLogCommand;

        public ICommand CopyTaskLogCommand =>
            _CopyTaskLogCommand
            ?? (
                _CopyTaskLogCommand = new RelayCommand<object>(o =>
                {
                    if (o == null)
                        return;
                    FFmpegTaskItem model = (FFmpegTaskItem)o;
                    Utils.CopyToClipboard(model.CopyLog(), "日志");
                   
                })
            );
        public ICommand ChangeDeviceTypeCommand =>
            _ChangeDeviceTypeCommand
            ?? (
                _ChangeDeviceTypeCommand = new RelayCommand<object>(o =>
                {
                    if (o == null)
                        return;

                    foreach (DeviceType dt in Utils.GetAllEnumValues<DeviceType>())
                    {
                        if (dt.ToString().Equals(o.ToString()))
                        {
                            DeviceType = dt;
                        }
                    }
                })
            );
        public ICommand CommandChangedCommand =>
            _CommandChangedCommand
            ?? (
                _CommandChangedCommand = new RelayCommand<object>(o =>
                {
                    if (o == null)
                        return;
                    CurrentFFmpegCommand = (FFmpegCommand)o;
                    if (CurrentFFmpegCommand.name.Equals(AppConstants.QuickTips))
                    {
                        CurrentFFmpegCommand = null;
                        CurrentCommand = null;
                        OutputEndWith = null;
                        CommandDesc = null;
                        return;
                    }
                    CurrentCommand = CurrentFFmpegCommand.cpu;
                    if (DeviceType == DeviceType.GPU)
                    {
                        CurrentCommand = CurrentFFmpegCommand.gpu;
                    }
                    OutputEndWith = CurrentFFmpegCommand.endwith;
                    CommandDesc = CurrentFFmpegCommand.desc;
                })
            );

        public ICommand DoCommand =>
            _DoCommand
            ?? (
                _DoCommand = new RelayCommand<object>(o =>
                {
                    if (o == null)
                    {
                        return;
                    }
                    if (string.IsNullOrEmpty(CurrentCommand) || string.IsNullOrEmpty(OutputEndWith) || FFmpegTaskQueueManager.Instance.Tasks.Where(t=>t.Status != TaskStatus.Completed).Count() == 0)
                    {
                        return;
                    }
                    FFmpegCommand command = CurrentFFmpegCommand.Clone();
                    command.gpu = CurrentCommand;
                    command.cpu = CurrentCommand;
                    command.endwith = OutputEndWith;
                    FFmpegTaskQueueManager.Instance.StartProcessing(command);

                    CommandListEnable = false;
                    ClearListEnable = false;
                    DeviceTypeEnable = false;
                    DoCommandEnable = false;
                    DeleteSelectedEnable = false;
                    CurrentCommandEnable = false;
                    OutputEndWithEnable = false;
                })
            );
        public ICommand OpenFileCommand =>
            _OpenFileCommand
            ?? (
                _OpenFileCommand = new RelayCommand<object>(o =>
                {
                    System.Windows.Forms.OpenFileDialog openFileDialog =
                        new System.Windows.Forms.OpenFileDialog();
                    string filter = "视频文件(";
                    foreach (string allow in AppConstants.__allowVideoFiles)
                    {
                        filter += "*." + allow + ",";
                    }
                    filter = filter.Remove(filter.LastIndexOf(","));
                    filter += ")|";
                    foreach (string allow in AppConstants.__allowVideoFiles)
                    {
                        filter += "*." + allow + ";";
                    }
                    filter = filter.Remove(filter.LastIndexOf(";"));
                    openFileDialog.Filter = filter;
                    openFileDialog.Multiselect = true;
                    openFileDialog.RestoreDirectory = true;
                    openFileDialog.FilterIndex = 1;
                    string[] files = null;
                    if (openFileDialog.ShowDialog() == DialogResult.OK)
                    {
                        files = openFileDialog.FileNames;
                    }
                    else
                    {
                        return;
                    }
                    TaskCount = "正在解析 " + files.Length + "个视频";
                    for (int i = 0; i < files.Length; i++)
                    {
                        string f = files[i];
                        int cnt = FFmpegTaskQueueManager.Instance.Tasks.Count;
                        FFmpegTaskQueueManager.Instance.AddTask(
                            f,
                            TaskAction,
                            TaskActionBefore,
                            TaskActionAfter
                        );
                    }

                    doneCount = FFmpegTaskQueueManager
                          .Instance.Tasks.Where(t => t.Status == TaskStatus.Completed)
                          .Count();
                    taskCount = FFmpegTaskQueueManager.Instance.Tasks.Count;
                    TaskCount = "总任务 " + taskCount;
                    DoneCount = "已完成 " + doneCount;
                })
            );

        private Action<CancellationToken, IProgress<double>, FFmpegTaskItem> TaskAction = (
            token,
            progress,
            item
        ) =>
        {
            try
            {
                Process process = new Process();
                token.Register(() =>
                {
                    if (!process.HasExited)
                    {
                        item.Killed = true;
                        process.Kill();
                    }
                });
                process.StartInfo.FileName = "ffmpeg ";
                process.StartInfo.WindowStyle = ProcessWindowStyle.Normal;
                string param = getParam(item);
                item.Info(AppConstants.DoubleClickMessage);
                item.Info("参数 ffmpeg " + param);
                process.StartInfo.Arguments = param;
                process.StartInfo.CreateNoWindow = true; //显示命令行窗口
                process.StartInfo.UseShellExecute = false;
                IDictionary<string, string?> environment = process.StartInfo.Environment;
                environment["taskItem_Path"] = item.Path;
                Task_Context.Add(item.Path, item);
                Progress_Context.Add(item.Path, progress);
                //将输出信息重定向
                process.StartInfo.RedirectStandardInput = true;
                process.StartInfo.RedirectStandardOutput = true;
                process.StartInfo.RedirectStandardError = true;
                process.EnableRaisingEvents = true;
                process.Exited += new EventHandler(scale_Exited);
                process.OutputDataReceived += new DataReceivedEventHandler(
                    scale_OutputDataReceived
                );
                process.ErrorDataReceived += new DataReceivedEventHandler(scale_ErrorDataReceived);
                process.Start();
                //开始异步读取输出
                process.BeginOutputReadLine();
                process.BeginErrorReadLine();
                process.WaitForExit();
            }
            catch (Exception e)
            {
                item.Status = TaskStatus.Failed;
            }
            finally { }
        };

        private Action<CancellationToken, FFmpegTaskItem> TaskActionBefore = (token, item) =>
        {
            Engine ffmpeg = new Engine(@"ffmpeg.exe");
            InputFile inputFile = new InputFile(item.Path);
            MetaData data = ffmpeg.GetMetaDataAsync(inputFile, token).GetAwaiter().GetResult();
            if (data == null)
            {
                //Info("视频有问题");
                return;
            }
            item.AudioChannel = data.AudioData.ChannelOutput;
            int seconds = (int)data.Duration.TotalSeconds;
            item.Duration = TimeSpan.FromSeconds(seconds);
            if (data.VideoData != null)
            {
                StringBuilder sb = new StringBuilder();
                Random random = new Random();
                TimeSpan ts = DateTime.UtcNow - new DateTime(1970, 1, 1, 0, 0, 0, 0);
                for (int i = 0; i < 10; i++)
                {
                    int index = random.Next(AppConstants.Chars.Length);
                    sb.Append(AppConstants.Chars[index]);
                }
                string currentImageFile =
                    "./"
                    + AppConstants.ImageFileStart
                    + Convert.ToInt64(ts.TotalMilliseconds).ToString()
                    + "_"
                    + sb.ToString()
                    + ".jpg";
                var outputFile = new OutputFile(currentImageFile);
                int durationMilliseconds = (int)data.Duration.TotalMilliseconds;
                ConversionOptions conversionOptions = new ConversionOptions()
                {
                    Seek = TimeSpan.FromMilliseconds(random.Next(1, durationMilliseconds - 1)),
                };

                ffmpeg
                    .GetThumbnailAsync(inputFile, outputFile, conversionOptions, token)
                    .GetAwaiter()
                    .GetResult();
                Image image = Image.FromFile(currentImageFile);
                item.FrameSize = image.Width + "x" + image.Height;
            }
        };

        static void scale_OutputDataReceived(Object sender, DataReceivedEventArgs e) { }

        static void scale_ErrorDataReceived(Object sender, DataReceivedEventArgs e)
        {
            Process process = (Process)sender;
            string path = process.StartInfo.Environment["taskItem_Path"];
            FFmpegTaskItem taskItem = Task_Context[path];
            IProgress<double> progress = Progress_Context[path];
            string info = e.Data;
            if (info == null)
            {
                return;
            }
            taskItem.Info(e.Data);
            foreach (
                string keyword in FFmpegCommands.Instance.CurrentFFmpegCommand.keywords.Split(',')
            )
            {
                if (!info.Contains(keyword))
                {
                    return;
                }
            }
            Match match = Regex.Match(info, FFmpegCommands.Instance.CurrentFFmpegCommand.regex);
            if (match.Success)
            {
                string time = match.Groups[1].Value;
                if (time.Contains("."))
                {
                    time = time.Split(".")[0];
                }
                string[] times = time.Split(":");
                int cur =
                    int.Parse(times[0]) * 60 * 60 + int.Parse(times[1]) * 60 + int.Parse(times[2]);

                TimeSpan Duration = taskItem.Duration;
                int duration = (int)Duration.TotalSeconds;
                double rx = (Convert.ToDouble(cur) / Convert.ToDouble(duration));
                taskItem.Progress = rx;
                DateTime now = DateTime.Now;
                taskItem.SpendTime = new TimeSpan(now.Ticks - taskItem.startTime.Ticks).ToString(
                    @"hh\:mm\:ss"
                );
                Serilog.Log.Information("进度 " + taskItem.Progress);
            }
        }

        static void scale_Exited(Object sender, EventArgs e)
        {
            Process process = (Process)sender;
            string path = process.StartInfo.Environment["taskItem_Path"];
            FFmpegTaskItem taskItem = Task_Context[path];
            taskItem.Info("任务退出" + path);
            Task_Context.Remove(path);
            Progress_Context.Remove(path);
        }

        private static string getParam(FFmpegTaskItem item)
        {
            //cpu和gpu命令已在上层处理好了
            string param = FFmpegCommands.Instance.CurrentCommand;
            param = param.Replace("ffmpeg ", "");
            param = param.Replace("#{input} ", "\"" + item.Path + "\" ");
            string path = System.IO.Path.GetDirectoryName(item.Path);
            FileInfo f = new FileInfo(item.Path);
            string output = f.Name.Remove(f.Name.LastIndexOf("."));
            output = "\"" + path + "\\" + output + FFmpegCommands.Instance.OutputEndWith + "\" ";
            param = param.Replace("#{output} ", output);
            item.OutPath = output;
            return param;
        }

        private Action<CancellationToken, FFmpegTaskItem> TaskActionAfter = (token, item) =>
        {
            var contentb = new NotificationContent()
            {
                Title = "视频处理成功,点击打开",
                Type = NotificationType.Information,
                Message = item.Path
            };
            AppManager.Instance.NotificationManager.Show(contentb, onClick: () => Process.Start(AppConfig.Instance.VideoPlayerPath, item.OutPath));
        };

        public ICommand StopAllCommand =>
            _StopAllCommand
            ?? (
                _StopAllCommand = new RelayCommand<object>(o =>
                {
                    FFmpegTaskQueueManager.Instance.StopAllTasks();
                    CommandListEnable = true;
                    ClearListEnable = true;
                    DeviceTypeEnable = true;
                    DoCommandEnable = true;
                    DeleteSelectedEnable = true;
                    CurrentCommandEnable = true;
                    OutputEndWithEnable = true;
                })
            );

        public ICommand DeleteSelectedCommand =>
            _DeleteSelectedCommand
            ?? (
                _DeleteSelectedCommand = new RelayCommand<object>(o =>
                {
                    FFmpegTaskQueueManager.Instance.DeleteSelected();
                    doneCount = FFmpegTaskQueueManager
                        .Instance.Tasks.Where(t => t.Status == TaskStatus.Completed)
                        .Count();
                    taskCount = FFmpegTaskQueueManager.Instance.Tasks.Count;
                    TaskCount = "总任务 " + taskCount;
                    DoneCount = "已完成 " + doneCount;
                })
            );
        public ICommand ClearListCommand =>
            _ClearListCommand
            ?? (
                _ClearListCommand = new RelayCommand<object>(o =>
                {
                    FFmpegTaskQueueManager.Instance.ClearTasks();
                    taskCount = 0;
                    doneCount = 0;
                    TaskCount = "总任务 " + taskCount;
                    DoneCount = "已完成 " + doneCount;
                })
            );

        public ICommand DeleteDoneCommand =>
            _DeleteDoneCommand
            ?? (
                _DeleteDoneCommand = new RelayCommand<object>(o =>
                {
                    FFmpegTaskQueueManager.Instance.ClearCompletedTasks();
                    taskCount = taskCount - doneCount;
                    doneCount = 0;
                    TaskCount = "总任务 " + taskCount;
                    DoneCount = "已完成 " + doneCount;
                })
            );
    }
}
